use super::{LexerError, Position, Token, TokenWrap};

macro_rules! return_none {
    () => {
        return None
    };
}

macro_rules! return_token {
    ($self: ident, $token: expr) => {
        return Some(Ok(TokenWrap::new($token, $self.current_position.clone())))
    };
}

macro_rules! return_error {
    ($self: ident, $message: expr) => {
        return Some(Err(LexerError {
            message: $message,
            position: $self.current_position.clone(),
        }))
    };
}

struct SysYLexer {
    str: Vec<char>,
    index: usize,
    current_position: Position,
}

impl SysYLexer {
    fn new(str: Vec<char>) -> Self {
        Self {
            str,
            index: 0,
            current_position: Position { line: 1, col: 1 },
        }
    }

    fn get_token(&mut self) -> Option<Result<TokenWrap, LexerError>> {
        loop {
            let char: char = match self.get_char() {
                Some(c) => *c,
                None => return_none!(),
            };

            match char {
                // check ident and keyword
                'a'..='z' | 'A'..='Z' => {
                    let mut buf = String::new();
                    buf.push(char);
                    self.next();
                    let mut char = match self.get_char() {
                        Some(c) => c,
                        None => return_token!(self, Token::Ident(buf)),
                    };
                    while let 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' = char {
                        buf.push(*char);
                        self.next();
                        char = match self.get_char() {
                            Some(c) => c,
                            None => break,
                        };
                    }
                    match buf.as_str() {
                        "const" => return_token!(self, Token::Const),
                        "int" => return_token!(self, Token::Int),
                        "void" => return_token!(self, Token::Void),
                        "if" => return_token!(self, Token::If),
                        "else" => return_token!(self, Token::Else),
                        "while" => return_token!(self, Token::While),
                        "break" => return_token!(self, Token::Break),
                        "continue" => return_token!(self, Token::Continue),
                        "return" => return_token!(self, Token::Return),
                        other => return_token!(self, Token::Ident(other.into())),
                    }
                }
                // check ident that begining of '_'
                '_' => {
                    let mut ident = String::new();
                    ident.push(char);
                    self.next();
                    let mut char = match self.get_char() {
                        Some(c) => *c,
                        None => return_token!(self, Token::Ident(ident)),
                    };
                    while let 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' = char {
                        ident.push(char);
                        self.next();
                        char = match self.get_char() {
                            Some(c) => *c,
                            None => return_token!(self, Token::Ident(ident)),
                        }
                    }
                    return_token!(self, Token::Ident(ident));
                }
                // check number
                '0'..='9' => {
                    let mut number_str: Vec<char> = Vec::new();
                    number_str.push(char);
                    if char == '0' {
                        self.next();
                        let char = match self.get_char() {
                            Some(c) => c,
                            None => return_token!(self, Token::IntegerConst(0)),
                        };
                        match char {
                            'x' | 'X' => {
                                // hex
                                let mut num_str: String = String::new();
                                self.next();
                                let mut char = match self.get_char() {
                                    Some(c) => c,
                                    None => {
                                        return_error!(self, format!("invalid number {}", num_str))
                                    }
                                };
                                while let '0'..='9' | 'a'..='f' | 'A'..='F' = char {
                                    num_str.push(*char);
                                    self.next();
                                    char = match self.get_char() {
                                        Some(c) => c,
                                        None => return_token!(
                                            self,
                                            Token::IntegerConst(
                                                i64::from_str_radix(&num_str, 16).unwrap_or_else(
                                                    |_| panic!("{} convert to i64", num_str)
                                                )
                                            )
                                        ),
                                    };
                                }
                                return_token!(
                                    self,
                                    Token::IntegerConst(
                                        i64::from_str_radix(&num_str, 16).unwrap_or_else(
                                            |_| panic!("{} convert to i64", num_str)
                                        )
                                    )
                                );
                            }
                            '0'..='7' => {
                                // oct
                                let mut num_str: String = String::new();
                                num_str.push(*char);
                                self.next();
                                let mut char = match self.get_char() {
                                    Some(c) => c,
                                    None => return_token!(self, Token::IntegerConst(0)),
                                };
                                while let '0'..='7' = char {
                                    num_str.push(*char);
                                    self.next();
                                    char = match self.get_char() {
                                        Some(c) => c,
                                        None => return_token!(
                                            self,
                                            Token::IntegerConst(
                                                i64::from_str_radix(&num_str, 8).unwrap_or_else(
                                                    |_| panic!("{} can't convert to i64", num_str)
                                                )
                                            )
                                        ),
                                    };
                                }
                                return_token!(
                                    self,
                                    Token::IntegerConst(
                                        i64::from_str_radix(&num_str, 8).unwrap_or_else(
                                            |_| panic!("{} can't convert to i64", num_str)
                                        )
                                    )
                                );
                            }
                            _ => return_token!(self, Token::IntegerConst(0)),
                        }
                    } else {
                        // check dec
                        let mut num_str = String::new();
                        num_str.push(char);
                        self.next();
                        let mut char =
                            match self.get_char() {
                                Some(c) => c,
                                None => return_token!(
                                    self,
                                    Token::IntegerConst(num_str.parse().unwrap_or_else(
                                        |_| panic!("{} cant convert to i64", num_str)
                                    ))
                                ),
                            };
                        while let '0'..='9' = char {
                            num_str.push(*char);
                            self.next();
                            char = match self.get_char() {
                                Some(c) => c,
                                None => return_token!(
                                    self,
                                    Token::IntegerConst(num_str.parse().unwrap_or_else(
                                        |_| panic!("{} cant convert to i64", num_str)
                                    ))
                                ),
                            };
                        }
                        return_token!(
                            self,
                            Token::IntegerConst(
                                num_str
                                    .parse()
                                    .unwrap_or_else(|_| panic!("{} cant convert to i64", num_str))
                            )
                        );
                    }
                }
                // check '/', "//", "/* */"
                '/' => {
                    self.next();
                    let char = match self.get_char() {
                        Some(c) => c,
                        None => return_token!(self, Token::Div),
                    };
                    match char {
                        '/' => {
                            self.skip_line();
                        }
                        '*' => {
                            // find next "*/"
                            // store index
                            let index = self.index;
                            self.next();
                            let mut char = match self.get_char() {
                                Some(c) => c,
                                None => {
                                    self.index = index;
                                    return_token!(self, Token::Div);
                                }
                            };

                            loop {
                                if *char == '*' {
                                    self.next();
                                    let char = match self.get_char() {
                                        Some(c) => c,
                                        None => {
                                            self.index = index;
                                            return_token!(self, Token::Div);
                                        }
                                    };
                                    if *char == '/' {
                                        // mutiline comment
                                        self.next();
                                        break;
                                    } else {
                                        self.prev();
                                    }
                                }
                                self.next();
                                char = match self.get_char() {
                                    Some(c) => c,
                                    None => {
                                        self.index = index;
                                        return_token!(self, Token::Div);
                                    }
                                };
                            }
                        }
                        _ => return_token!(self, Token::Div),
                    }
                }
                '!' => {
                    self.next();
                    match self.get_char() {
                        Some(char) => {
                            if *char == '=' {
                                self.next();
                                return_token!(self, Token::Neq);
                            } else {
                                return_token!(self, Token::Not);
                            }
                        }
                        None => return_token!(self, Token::Not),
                    }
                }
                // check "==" and "="
                '=' => {
                    self.next();
                    match self.get_char() {
                        Some(char) => {
                            if *char == '=' {
                                self.next();
                                return_token!(self, Token::Eq);
                            } else {
                                return_token!(self, Token::Assign);
                            }
                        }
                        None => return_token!(self, Token::Assign),
                    }
                }
                // check "<", "<="
                '<' => {
                    self.next();
                    match self.get_char() {
                        Some(char) => {
                            if *char == '=' {
                                self.next();
                                return_token!(self, Token::Le);
                            } else {
                                return_token!(self, Token::Lt);
                            }
                        }
                        None => return_token!(self, Token::Lt),
                    }
                }
                '>' => {
                    self.next();
                    match self.get_char() {
                        Some(char) => {
                            if *char == '=' {
                                self.next();
                                return_token!(self, Token::Ge);
                            } else {
                                return_token!(self, Token::Gt);
                            }
                        }
                        None => return_token!(self, Token::Gt),
                    }
                }
                // check "77==&&"
                '&' => {
                    self.next();
                    match self.get_char() {
                        Some(char) => {
                            if *char == '&' {
                                self.next();
                                return_token!(self, Token::And);
                            } else {
                                return_error!(self, format!("invalid &"));
                            }
                        }
                        None => return_error!(self, format!("invalid &")),
                    }
                }
                // check "||"
                '|' => {
                    self.next();
                    match self.get_char() {
                        Some(char) => {
                            if *char == '|' {
                                self.next();
                                return_token!(self, Token::Or);
                            } else {
                                return_error!(self, format!("invalid |"))
                            }
                        }
                        None => return_error!(self, format!("invalid |")),
                    }
                }
                '+' => {
                    self.next();
                    return_token!(self, Token::Plus);
                }
                '-' => {
                    self.next();
                    return_token!(self, Token::Minus);
                }
                '*' => {
                    self.next();
                    return_token!(self, Token::Mul);
                }
                '%' => {
                    self.next();
                    return_token!(self, Token::Mod);
                }
                // check "("
                '(' => {
                    self.next();
                    return_token!(self, Token::LParen);
                }
                ')' => {
                    self.next();
                    return_token!(self, Token::RParen);
                }
                '{' => {
                    self.next();
                    return_token!(self, Token::LBrace);
                }
                '}' => {
                    self.next();
                    return_token!(self, Token::RBrace);
                }
                '[' => {
                    self.next();
                    return_token!(self, Token::LBrackt);
                }
                ']' => {
                    self.next();
                    return_token!(self, Token::RBrackt);
                }
                ',' => {
                    self.next();
                    return_token!(self, Token::Comma);
                }
                ';' => {
                    self.next();
                    return_token!(self, Token::Semicolon);
                }
                // skip whitespace
                ' ' | '\n' | '\r' | '\t' => self.next(),
                other => {
                    self.next();
                    return_error!(self, format!("invalid {}", other));
                }
            }
        }
    }

    fn get_char(&self) -> Option<&char> {
        self.str.get(self.index)
    }

    fn next(&mut self) {
        let char = match self.get_char() {
            Some(c) => *c,
            None => return,
        };
        match char {
            '\n' => {
                self.current_position.line += 1;
                self.current_position.col = 1;
            }
            _ => {
                self.current_position.col += 1;
            }
        }
        self.index += 1;
    }

    fn prev(&mut self) {
        self.index -= 1;
        let char = match self.get_char() {
            Some(c) => *c,
            None => return,
        };
        match char {
            '\n' => {
                self.current_position.line -= 1;
                self.current_position.col = 1;
            }
            _ => {
                self.current_position.col -= 1;
            }
        }
    }

    fn skip_line(&mut self) {
        while match self.get_char() {
            Some(c) => c,
            None => return,
        } != &'\n'
        {
            self.next();
        }
        self.next();
    }
}

pub fn tokenize(input: &str) -> (Vec<TokenWrap>, Vec<LexerError>) {
    let mut token_list: Vec<TokenWrap> = Vec::new();
    let mut error_list: Vec<LexerError> = Vec::new();
    let mut lexer = SysYLexer::new(input.chars().collect());
    while let Some(result) = lexer.get_token() {
        match result {
            Ok(token) => token_list.push(token),
            Err(error) => error_list.push(error),
        }
    }
    (token_list, error_list)
}
